#include <dicom/storage/encrypt.h>
#include <dicom/storage/decrypt.h>
#include <gwbase/base/util.h>
#include <gwbase/base/test_harness.h>
#include <gwbase/base/test_util.h>
#include <dicom/upload/hash_cache.h>

using namespace dicom;

class HashCacheTest {};

static void CreateFakeHashEntry(int i, HashEntry& e1) {
  e1.src_md5 = StringPrintf("src_md5_%d", i);
  e1.src_size = i;
  e1.src_digest = StringPrintf("src_digest_%d", i);
  e1.src_sha512 = StringPrintf("src_sha512_%d", i);
  e1.hexkey = StringPrintf("hexkey_%d", i);
  e1.enc_size = i;
  e1.enc_digest = StringPrintf("enc_digest_%d", i);
  e1.status = HASH_LOCAL;
}

static void CreateFakeDownloadedHashEntry(int i, HashEntry& e1) {
  e1.src_md5 = StringPrintf("src_md5_%d", i);
  e1.src_size = -1;
  e1.hexkey = StringPrintf("hexkey_%d", i);
  e1.status = HASH_DOWNLOADED;
}

static bool LoadFakeHashEntry(HashCache* c, int i, HashEntry* e1) {
  std::string src_md5 = StringPrintf("src_md5_%d", i);
  std::string hexkey = StringPrintf("hexkey_%d", i);
  return c->GetHashEntry(src_md5, hexkey, e1);
}

static bool FindBestFakeHashEntry(HashCache* c, int i, HashEntry* e1) {
  std::string src_md5 = StringPrintf("src_md5_%d", i);
  return c->GetBestEntry(src_md5, e1);
}

TEST(HashCacheTest, SimpleTest) {
  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);

  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  for (int i = 0; i < 100; ++i) {
    HashEntry e1;
    CreateFakeHashEntry(i, e1);
    e1.status = HASH_IS_EXISTS;
    EXPECT_TRUE(c.UpdateHashEntry(&e1));
  }

  for (int i = 0; i < 100; ++i) {
    HashEntry e1;
    CreateFakeHashEntry(i, e1);
    e1.status = HASH_IS_EXISTS;

    HashEntry out;
    EXPECT_TRUE(LoadFakeHashEntry(&c, i, &out));

    EXPECT_TRUE(HashEntryEqual(e1, out));
  }

  for (int i = 0; i < 100; ++i) {
    std::string src_md5 = StringPrintf("src_md5_%d", i);
    std::string hexkey = StringPrintf("hexkey_%d", i);

    HashEntry e1;
    CreateFakeHashEntry(i, e1);
    HashEntry out;

    e1.status = HASH_LOCAL;
    EXPECT_TRUE(c.UpdateHashEntry(&e1));
    EXPECT_TRUE(c.GetHashEntry(src_md5, hexkey, &out));
    EXPECT_TRUE(out.status == HASH_IS_EXISTS);

    e1.status = HASH_IS_EXISTS;
    EXPECT_TRUE(c.UpdateHashEntry(&e1));
    EXPECT_TRUE(c.GetHashEntry(src_md5, hexkey, &out));
    EXPECT_TRUE(out.status == HASH_IS_EXISTS);

    e1.status = HASH_UPLOAD;
    EXPECT_TRUE(c.UpdateHashEntry(&e1));
    EXPECT_TRUE(c.GetHashEntry(src_md5, hexkey, &out));
    EXPECT_TRUE(out.status == HASH_UPLOAD);
  }
}

TEST(HashCacheTest, BestHexKeyTest) {
  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);

  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  HashEntry out;
  EXPECT_TRUE(FindBestFakeHashEntry(&c, 1, &out) == false);

  HashEntry e1;
  CreateFakeHashEntry(1, e1);
  e1.status = HASH_LOCAL;
  EXPECT_TRUE(c.UpdateHashEntry(&e1));

  EXPECT_TRUE(FindBestFakeHashEntry(&c, 1, &out));
  EXPECT_TRUE(HashEntryEqual(e1, out));

  HashEntry e2;
  CreateFakeHashEntry(2, e2);
  e2.src_md5 = e1.src_md5;
  e2.status = HASH_IS_EXISTS;
  EXPECT_TRUE(c.UpdateHashEntry(&e2));
  EXPECT_TRUE(FindBestFakeHashEntry(&c, 1, &out));
  EXPECT_TRUE(HashEntryEqual(e2, out));

  HashEntry e3;
  CreateFakeHashEntry(3, e3);
  e3.src_md5 = e1.src_md5;
  e3.status = HASH_UPLOAD;
  EXPECT_TRUE(c.UpdateHashEntry(&e3));
  EXPECT_TRUE(FindBestFakeHashEntry(&c, 1, &out));
  EXPECT_TRUE(HashEntryEqual(e3, out));
}

TEST(HashCacheTest, UpdatedDownloadedEntry) {
  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);

  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  HashEntry e1;
  HashEntry out1;
  CreateFakeDownloadedHashEntry(1, e1);
  EXPECT_TRUE(c.UpdateHashEntry(&e1));
  EXPECT_TRUE(LoadFakeHashEntry(&c, 1, &out1));
  EXPECT_TRUE(HashEntryEqual(e1, out1));

  HashEntry e2;
  HashEntry out2;
  CreateFakeHashEntry(1, e2);
  e2.status = HASH_LOCAL;
  EXPECT_TRUE(c.UpdateHashEntry(&e2));
  EXPECT_TRUE(LoadFakeHashEntry(&c, 1, &out2));
  e2.status = HASH_DOWNLOADED;
  EXPECT_TRUE(HashEntryEqual(e2, out2));

  HashEntry e3;
  HashEntry out3;
  CreateFakeHashEntry(1, e3);
  e3.status = HASH_IS_EXISTS;
  EXPECT_TRUE(c.UpdateHashEntry(&e3));
  EXPECT_TRUE(LoadFakeHashEntry(&c, 1, &out3));
  EXPECT_TRUE(HashEntryEqual(e3, out3));
}

TEST(HashCacheTest, UpdatedDownloadedEntry2) {
  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);

  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  HashEntry e1;
  HashEntry out1;
  CreateFakeDownloadedHashEntry(1, e1);
  EXPECT_TRUE(c.UpdateHashEntry(&e1));
  EXPECT_TRUE(LoadFakeHashEntry(&c, 1, &out1));
  EXPECT_TRUE(HashEntryEqual(e1, out1));

  HashEntry e2;
  HashEntry out2;
  CreateFakeHashEntry(1, e2);
  e2.status = HASH_LOCAL;
  EXPECT_TRUE(c.UpdateHashEntry(&e2));
  EXPECT_TRUE(LoadFakeHashEntry(&c, 1, &out2));
  e2.status = HASH_DOWNLOADED;
  EXPECT_TRUE(HashEntryEqual(e2, out2));

  HashEntry e3;
  HashEntry out3;
  CreateFakeHashEntry(1, e3);
  e3.status = HASH_IS_EXISTS;
  EXPECT_TRUE(c.UpdateHashEntry(&e3));
  EXPECT_TRUE(LoadFakeHashEntry(&c, 1, &out3));
  EXPECT_TRUE(HashEntryEqual(e3, out3));
}

TEST(HashCacheTest, GetEncrytFileHashNotInCache) {
  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);

  const std::string key = dicom::ByteArray::genRandom(32).toHexStr();
  const char* src_file = "src_file.dat";
  const char* enc_file = "enc_file.dat";

  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  CreateTestFile(src_file, 0, true);

  HashEntry h1;
  s = dicom::ZipAndEncryptFile(key, src_file, enc_file, &h1);
  h1.status = HASH_LOCAL;
  EXPECT_TRUE(s.ok());

  HashEntry h2;
  s = dicom::GetEncrytFileHash(&c, enc_file, h1.src_md5, h1.hexkey, &h2);
  EXPECT_TRUE(s.ok());

  EXPECT_TRUE(HashEntryEqual(h1, h2));
}

TEST(HashCacheTest, GetEncrytFileHashInCache) {
  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);

  const std::string key = dicom::ByteArray::genRandom(32).toHexStr();
  const char* src_file = "src_file.dat";
  const char* enc_file = "enc_file.dat";

  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  CreateTestFile(src_file, 0, true);

  HashEntry h1;
  s = dicom::ZipAndEncryptFile(key, src_file, enc_file, &h1);
  h1.status = HASH_LOCAL;
  c.UpdateHashEntry(&h1);
  EXPECT_TRUE(s.ok());

  HashEntry h2;
  s = dicom::GetEncrytFileHash(&c, enc_file, h1.src_md5, h1.hexkey, &h2);
  EXPECT_TRUE(s.ok());

  EXPECT_TRUE(HashEntryEqual(h1, h2));
}

int main() {
  dicom::RunAllTests();
  return 0;
}
